//
//  Observer+ZZ.swift
//  ZZToolKit
//
//  Created by 陈钟 on 2019/12/25.
//  Copyright © 2019 陈钟. All rights reserved.
//

import Foundation
import UIKit

public extension NSObject{

    typealias ObserverBlockTargetAlias = (_ value: ZZObserveValue) -> Void

    struct ZZObserveValue {
        public var keyPath: String?
        public var object: Any?
        public var change: [NSKeyValueChangeKey : Any]?
        public var context: UnsafeMutableRawPointer?

        public init(keyPath: String?, object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?){
            self.keyPath = keyPath
            self.object = object
            self.change = change
            self.context = context
        }
    }

    class ObserverBlockTarget: NSObject {

        struct TagBlock {
            var block: ObserverBlockTargetAlias
            var key: String = ""
            init(key: String = "normal", block: @escaping ObserverBlockTargetAlias) {
                self.key = key
                self.block = block
            }
        }

        deinit {
            self.targetDeinit?()
        }

        var targetDeinit: (() -> ())?
        private var blocks: [TagBlock] = []

        open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
            self.blocks.forEach({
                $0.block(ZZObserveValue(keyPath: keyPath, object: object, change: change, context: context))
            })
        }

        func append(block: TagBlock){
            self.blocks.append(block)
        }

        init(block: TagBlock) {
            super.init()
            self.blocks.append(block)
        }

        @discardableResult
        func remoBlock(for key: String) -> [TagBlock]{
            self.blocks.removeAll(where: {$0.key == key})
            return self.blocks
        }
    }

    var observerTargets: [String: ObserverBlockTarget] {
        set{
            let key: UnsafeRawPointer! = UnsafeRawPointer.init(bitPattern: "observerTargets".hashValue)
            objc_setAssociatedObject(self, key, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
        get{
            let key: UnsafeRawPointer! = UnsafeRawPointer.init(bitPattern: "observerTargets".hashValue)
            let obj = objc_getAssociatedObject(self, key)
            return (obj as? [String: ObserverBlockTarget]) ?? [:]
        }
    }
    
    func addObservers(keyPaths:[String], key: String = "", options: NSKeyValueObservingOptions = [.old,.new], block:@escaping ObserverBlockTargetAlias) {
        keyPaths.forEach {[weak self] (keypath) in
            guard let `self` = self else { return }
            self.addObserver(keyPath: keypath, key: key, options: options, block: block)
        }
    }

    func addObserver(keyPath: String, key: String = "", options: NSKeyValueObservingOptions = [.old,.new], block:@escaping ObserverBlockTargetAlias){
        if let blockArr = self.observerTargets[keyPath] {
            let tagblock = ObserverBlockTarget.TagBlock(key: key ,block: block)
            blockArr.append(block: tagblock)
        }else{
            let tagblock = ObserverBlockTarget.TagBlock(key: key, block: block)
            let tag = ObserverBlockTarget.init(block: tagblock)
            tag.targetDeinit = { [weak self] in
                self?.removeObserver(keyPath: keyPath)
            }
            self.observerTargets[keyPath] = tag
            self.addObserver(tag, forKeyPath: keyPath, options: options, context: nil)
        }
    }

    func removeObserver(keyPath: String, key: String){
        guard let keyTargets = self.observerTargets[keyPath] else { return }
        let blocks = keyTargets.remoBlock(for: key)
        if blocks.count <= 0 {
            self.removeObserver(keyPath: keyPath)
        }
    }

    func removeObserver(keyPaths: [String], key: String){
        keyPaths.forEach({self.removeObserver(keyPath: $0, key: key)})
    }

    func removeObserver(keyPath: String){
        guard let keyTargets = self.observerTargets[keyPath] else { return }
        self.removeObserver(keyTargets, forKeyPath: keyPath)
        self.observerTargets.removeValue(forKey: keyPath)
    }

    func remoAllObservers(){
        self.observerTargets.forEach { (keyPath, keyTargets) in
            self.removeObserver(keyTargets, forKeyPath: keyPath)
        }
        self.observerTargets.removeAll()
    }

}
